package org.kgduyjj.toolkit.algorithm;

import java.util.stream.Stream;

import org.kgduyjj.toolkit.Arith;
import org.kgduyjj.toolkit.util.ObjectUtils;
import org.kgduyjj.toolkit.util.StringUtils;

/**
 * 自校验算法
 * 
 * @author Kgduyjj
 *
 */
public class SelfCheckCoder {
    /** 自校验码长度 */
    protected static final int SELF_CHECK_CODE_LEN = 18;
    // 本体码长度校验范围
    /** 本体码最大长度 */
    protected static final int MAX_OWNCODE_LEN = 31;
    /** 本体码最小长度 */
    protected static final int MIN_OWNCODE_LEN = 7;
    /** 本体码 乘积因子工厂 */
    protected static final int[] POWER_FACTORS = { 7, 3, 4, 7, 2, 3, 0, 6, 7, 8, 9, 3, 4, 9, 10, 1, 5, 8, 4, 3, 2, 7, 5,
            9, 8, 5, 3, 4, 9, 0, 3, 9 };
    /** 自校验位 */
    protected static final String[] CHECK_CODES = { "8", "3", "6", "9", "2", "5", "7" };
    /** 自校验位 取模计算值 */
    protected static final int CHECK_MORO = 7;

    /**
     * 校验是否为纯数字字符串
     * 
     * @param str
     * @return
     */
    private static boolean isDigital(String str) {
        return (str == null || "".equals(str) ? false : str.matches("^[0-9]*$"));
    }

    /**
     * 检查自校验码长度
     * 
     * @param selfCheckCode
     * @return
     */
    private static boolean validLenght4SCC(String selfCheckCode) {
        return (selfCheckCode != null
                && !Arith.overBounds(selfCheckCode.length() - 1, MIN_OWNCODE_LEN, MAX_OWNCODE_LEN));
    }

    /**
     * 检查本体码长度
     * 
     * @param selfCheckCode
     * @return
     */
    private static boolean validLenght4OC(String ownCode) {
        return (ownCode != null && !checkOwnCodeLenght(ownCode.length()));
    }

    /**
     * 
     * @param ownCodeLen
     * @return
     */
    private static boolean checkOwnCodeLenght(int ownCodeLen) {
        return Arith.overBounds(ownCodeLen, MIN_OWNCODE_LEN, MAX_OWNCODE_LEN);
    }

    /**
     * 提取本体码
     * 
     * @param validateCode
     * @return
     */
    private static String fetchOwnCode(String selfCheckCode) {
        if (!validLenght4SCC(selfCheckCode)) {
            return null;
        }
        return selfCheckCode.substring(0, selfCheckCode.length() - 1);
    }

    /**
     * 根据本体码计算一个结果值
     * 
     * @param ownCode
     * @return
     */
    private static Integer sumOwnCode(String ownCode) {
        if (!validLenght4OC(ownCode) && !isDigital(ownCode)) {
            return null;
        }
        // 本体码转换为数字数组
        Integer[] powerItems = Stream.of(ownCode.split("")).map(item -> Integer.parseInt(item))
                .filter(ObjectUtils::notNull).toArray(Integer[]::new);
        // 转换结果长度校验
        if (!checkOwnCodeLenght(powerItems.length)) {
            return null;
        }
        int powerSum = 0;
        for (int i = 0; i < powerItems.length; i++) {
            powerSum += powerItems[i] * POWER_FACTORS[i];
        }
        return powerSum;
    }

    private static String fetchCheckCode(String selfCheckCode) {
        if (StringUtils.isBlank(selfCheckCode)) {
            return null;
        }
        return selfCheckCode.substring(selfCheckCode.length() - 1, selfCheckCode.length());
    }

    /**
     * 根据本体码生成校验码
     * 
     * @param iSum
     * @return
     */
    public static String generateCheckCode(String ownCode) {
        Integer sum = sumOwnCode(ownCode);
        if (ObjectUtils.isNull(sum) || Arith.isLessThenZero(sum)) {
            return null;
        }
        int chkIdx = sum % CHECK_MORO;
        if (chkIdx > CHECK_CODES.length) {
            return null;
        }
        return CHECK_CODES[chkIdx];
    }

    /**
     * 根据本体码生成完整的自校验字符串
     * 
     * <li>MIN: {@link #MIN_OWNCODE_LEN} + 1</li>
     * <li>MAX: {@link #MAX_OWNCODE_LEN} + 1</li>
     * 
     * @param ownCode
     * @return
     */
    public static String generate(String ownCode) {
        if (!validLenght4OC(ownCode)) {
            return null;
        }
        String chkCode = generateCheckCode(ownCode);
        if (StringUtils.isBlank(chkCode)) {
            return null;
        }
        return StringUtils.format("{}{}", ownCode, chkCode);
    }

    public static boolean validate(String selfCheckCode) {
        if (StringUtils.isBlank(selfCheckCode)) {
            return false;
        }
        String checkCode = fetchCheckCode(selfCheckCode);
        if (StringUtils.isBlank(checkCode)) {
            return false;
        }
        String gChkCode = generateCheckCode(fetchOwnCode(selfCheckCode));
        if (StringUtils.isBlank(gChkCode)) {
            return false;
        }
        return checkCode.contentEquals(gChkCode);
    }
}
